package com.doge.walknovel.widgets.imagecropper

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.app.Activity
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.Matrix
import android.media.ExifInterface
import android.net.Uri
import android.util.AttributeSet
import android.view.*
import android.widget.*
import androidx.annotation.NonNull
import androidx.annotation.Nullable
import com.doge.wnpro.R
import com.doge.walknovel.utils.log
import com.doge.walknovel.utils.toast
import java.io.Closeable
import java.io.IOException

class ImageCropper @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs), GestureDetector.OnGestureListener, View.OnClickListener {
    private var iBack: View? = null
    private var iSubmit: View? = null
    private val iSource: ImageView
    private val vOverlay: OverlayView
    private val tTitle: TextView
    private var initMinScale = 0f
    private var mOutputWidth = 0
    private var mOutputHeight = 0
    private var mTag: String? = null
    var isInAnimation = false
    /**
     * @param outputFixedSize 是否按照vImageCropper.crop()方法入参outputWidth和outputHeight来输出，false表示仅只按其比例输出，true为绝对尺寸输出
     */
    var isOutputFixedSize = false
    private var isMultiTouch = false
    private var mLastScale = 0f
    private var mLastTranslateX = 0f
    private var mLastTranslateY = 0f
    private var mLastFingersDistance = 0f
    private var mLastFingersCenterX = 0f
    private var mLastFingersCenterY = 0f
    private val mGestureDetector: GestureDetector
    private var mCallback: Callback? = null
    private var animRestore: ViewPropertyAnimator? = null
    private var bmpSource: Bitmap? = null

    fun setTranslucentStatus(statusBarHeight: Int) {
        findViewById<View>(R.id.viewFit).layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight)
    }

    @Throws(Throwable::class)
    private fun cropInternal(sourceUri: Uri, outputWidth: Int, outputHeight: Int, isCircleOverlay: Boolean, tag: String) {
        log("cropper cropInternal sourceUri $sourceUri outputWidth $outputWidth outputHeight $outputHeight isCircleOverlay $isCircleOverlay tag $tag width $width height $height")
        val mWidth = width
        val mHeight = height
        if (mWidth * mHeight == 0) return
        visibility = View.VISIBLE
        mTag = tag
        mOutputWidth = outputWidth
        mOutputHeight = outputHeight
        vOverlay.reset(mOutputWidth, mOutputHeight, isCircleOverlay)
        bmpSource = obtainBmpSource(context, sourceUri, mOutputWidth, mOutputHeight)
        if (1f * bmpSource!!.width / bmpSource!!.height > 1f * iSource.width / iSource.height) {
            val bitmapHeightAfterFitCenter = (1f * bmpSource!!.height * iSource.width / bmpSource!!.width).toInt()
            bmpSource = Bitmap.createScaledBitmap(bmpSource!!, iSource.width, bitmapHeightAfterFitCenter, true)
            val initMinScaleX: Float = 1f * vOverlay.overlayWidth / iSource.width
            val initMinScaleY: Float = 1f * vOverlay.overlayHeight / bitmapHeightAfterFitCenter
            initMinScale = Math.max(initMinScaleX, initMinScaleY)
        } else {
            val bitmapWidthAfterFitCenter = (1f * bmpSource!!.width * iSource.height / bmpSource!!.height).toInt()
            bmpSource = Bitmap.createScaledBitmap(bmpSource!!, bitmapWidthAfterFitCenter, iSource.height, true)
            val initMinScaleX: Float = 1f * vOverlay.overlayWidth / bitmapWidthAfterFitCenter
            val initMinScaleY: Float = 1f * vOverlay.overlayHeight / iSource.height
            initMinScale = Math.max(initMinScaleX, initMinScaleY)
        }
        val defaultScale: Float = if (initMinScale > 1) initMinScale else 1f
        iSource.translationX = 0f
        iSource.translationY = 0f
        iSource.scaleX = defaultScale
        iSource.scaleY = defaultScale
        iSource.setImageBitmap(bmpSource)
    }

    /**
     * 裁剪图片(输出的图片默认只有比例是按照入参outputWidth和outputHeight获得的，尺寸大小并没有按照指定的尺寸大小。<br></br>
     * 如果要求大小也为入参请调用[ImageCropper.setOutputFixedSize] 设置值为true) <br></br>
     * 逻辑见[ImageCropper.onClick] output = mOutputWidth * mOutputHeight != 0 ? Bitmap.createScaledBitmap(clip, mOutputWidth, mOutputHeight, true) : clip <br></br>
     * ps: createScaledBitmap (小图变大图会造成内容的失真) 55555555<br></br>
     *
     * @param sourceUri       原图片路径
     * @param outputWidth     输出宽度
     * @param outputHeight    输出高度
     * @param isCircleOverlay 遮罩蒙板是否为圆形，为圆形的条件时在isCircleOverlay为true的同时，outputWidth等于outputHeight才行
     * @param tag             若同一界面有多处裁剪功能，对此传递一个tag标志避免混淆
     */
    fun crop(sourceUri: Uri, outputWidth: Int, outputHeight: Int, isCircleOverlay: Boolean, tag: String) {
        log("cropper crop sourceUri $sourceUri outputWidth $outputWidth outputHeight $outputHeight isCircleOverlay $isCircleOverlay tag $tag")
        ImageCropperHelper.startCrop(this)

        postDelayed({
            try {
                cropInternal(sourceUri, outputWidth, outputHeight, isCircleOverlay, tag)
            } catch (e: Throwable) {
                e.printStackTrace()
                if (e is OutOfMemoryError) {
                    toast("crop failure [1099]")
                    System.gc()
                } else {
                    toast("crop failure [1088]")
                }
                animExit()
            }
        }, 100)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        val action = event.action and MotionEvent.ACTION_MASK
        when (action) {
            MotionEvent.ACTION_UP -> {
                val startScale = iSource.scaleX
                val endScale: Float
                endScale = if (startScale < initMinScale) initMinScale else startScale
                val edgeTranslateX: Float = (bmpSource!!.width * endScale - vOverlay.overlayWidth) / 2
                val startTranslateX = iSource.translationX
                val endTranslateX: Float
                endTranslateX = if (startTranslateX > edgeTranslateX) edgeTranslateX else if (startTranslateX < -edgeTranslateX) -edgeTranslateX else startTranslateX
                val edgeTranslateY: Float = (bmpSource!!.height * endScale - vOverlay.overlayHeight) / 2
                val startTranslateY = iSource.translationY
                val endTranslateY: Float
                endTranslateY = if (startTranslateY > edgeTranslateY) edgeTranslateY else if (startTranslateY < -edgeTranslateY) -edgeTranslateY else startTranslateY
                if (endScale == startScale && endTranslateY == startTranslateY && endTranslateX == startTranslateX) return mGestureDetector.onTouchEvent(event)
                if (animRestore != null) animRestore?.cancel()
                animRestore = iSource.animate().scaleX(endScale).scaleY(endScale).translationX(endTranslateX)
                        .translationY(endTranslateY)
                animRestore?.start()
            }
            MotionEvent.ACTION_POINTER_DOWN -> if (!isMultiTouch) {
                isMultiTouch = true
                mLastFingersDistance = 0f
                mLastFingersCenterX = 0f
                mLastFingersCenterY = 0f
                mLastScale = iSource.scaleX
                mLastTranslateX = iSource.translationX
                mLastTranslateY = iSource.translationY
            }
            MotionEvent.ACTION_POINTER_UP -> if (event.pointerCount - 1 < 1 + 1) {
                isMultiTouch = false
            }
        }
        return mGestureDetector.onTouchEvent(event)
    }

    override fun onDown(e: MotionEvent): Boolean {
        mLastScale = iSource.scaleX
        mLastTranslateX = iSource.translationX
        mLastTranslateY = iSource.translationY
        return true
    }

    override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
        try {
            if (!isMultiTouch) {
                iSource.translationX = iSource.translationX - distanceX
                iSource.translationY = iSource.translationY - distanceY
            } else {
                val deltaX = e2.getX(1) - e2.getX(0)
                val deltaY = e2.getY(1) - e2.getY(0)
                val distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY.toDouble()).toFloat()
                if (mLastFingersDistance == 0f) mLastFingersDistance = distance
                val changedScale = (distance - mLastFingersDistance) / (width * 0.8f)
                var changedScaleValue = mLastScale + changedScale
                if (changedScaleValue < initMinScale / 2) changedScaleValue = initMinScale / 2
                iSource.scaleX = changedScaleValue
                iSource.scaleY = changedScaleValue
                val centerX = (e2.getX(1) + e2.getX(0)) / 2
                val centerY = (e2.getY(1) + e2.getY(0)) / 2
                if (mLastFingersCenterX == 0f && mLastFingersCenterY == 0f) {
                    mLastFingersCenterX = centerX
                    mLastFingersCenterY = centerY
                }
                val changedCenterX = centerX - mLastFingersCenterX
                iSource.translationX = mLastTranslateX + changedCenterX
                val changedCenterY = centerY - mLastFingersCenterY
                iSource.translationY = mLastTranslateY + changedCenterY
            }
        } catch (ex: IllegalArgumentException) {
            ex.printStackTrace()
        }
        return false
    }

    override fun onSingleTapUp(e: MotionEvent): Boolean {
        return false
    }

    override fun onShowPress(e: MotionEvent) {}
    override fun onLongPress(e: MotionEvent) {}
    override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
        return false
    }

    override fun onClick(v: View) {
        if (animRestore != null) animRestore!!.cancel()
        if (isInAnimation) return
        if (v === iSubmit) {
            if (mCallback != null) {
                val x: Float = ((bmpSource!!.width * iSource.scaleX - vOverlay.overlayWidth) / 2 - iSource.translationX) / iSource.scaleX
                val width: Float = vOverlay.overlayWidth / iSource.scaleX
                val y: Float = ((bmpSource!!.height * iSource.scaleY - vOverlay.overlayHeight) / 2 - iSource.translationY) / iSource.scaleY
                val height: Float = vOverlay.overlayHeight / iSource.scaleY
                val clip = Bitmap.createBitmap(bmpSource!!, x.toInt(), y.toInt(), width.toInt(), height.toInt())
                val output = if (isOutputFixedSize && mOutputWidth * mOutputHeight != 0) Bitmap.createScaledBitmap(clip, mOutputWidth, mOutputHeight, true) else clip
                mCallback!!.onPictureCropOut(output, mTag)
                animExit()
            }
        } else if (v === iBack) {
            animExit()
        }
    }

    private fun animExit() {
        ImageCropperHelper.overCrop()
        animate().alpha(0f).setListener(object : AnimatorListenerAdapter() {
            override fun onAnimationStart(animation: Animator) {
                super.onAnimationStart(animation)
                isInAnimation = true
            }

            override fun onAnimationEnd(animation: Animator) {
                super.onAnimationEnd(animation)
                isInAnimation = false
                visibility = View.GONE
                alpha = 1f
//                val currentActivity = (context as Activity).javaClass.simpleName
                //cEventBus.getDefault().post(BusEvent(Bus.INSTANCE.getREFRESH_STATUS_BAR_THEME(), currentActivity))
            }
        }).start()
    }

    fun handleBackPressed(): Boolean {
        if (View.GONE != visibility) {
            iBack!!.performClick()
            return true
        }
        return false
    }

    interface Callback {
        fun onPictureCropOut(bitmap: Bitmap, tag: String?)
    }

    fun setCallback(cb: Callback?) {
        mCallback = cb
    }

    internal class Helper private constructor(activity: Activity) {
        private val activityDecorView: ViewGroup
        private val mImageCropper: ImageCropper
        fun setCallback(cb: Callback?): Helper {
            mImageCropper.setCallback(cb)
            return this
        }

        fun setOutputFixedSize(isOutputFixedSize: Boolean): Helper {
            mImageCropper.isOutputFixedSize = (isOutputFixedSize)
            return this
        }

        fun setTitle(title: String?): Helper {
            mImageCropper.tTitle.text = title
            return this
        }

        fun setTranslucentStatusHeight(translucentStatusHeight: Int): Helper {
            mImageCropper.setTranslucentStatus(translucentStatusHeight)
            return this
        }

        fun create(): ImageCropper {
            removeExistingOverlayInView(activityDecorView)
            activityDecorView.addView(mImageCropper)
            return mImageCropper
        }

        private fun removeExistingOverlayInView(parent: ViewGroup) {
            for (i in 0 until parent.childCount) {
                val child: View? = parent.getChildAt(i)
                if (child?.id == VIEW_IMAGE_CROPPER_ID) {
                    parent.removeView(child)
                }
                if (child is ViewGroup) {
                    removeExistingOverlayInView(child)
                }
            }
        }

        companion object {
            private val VIEW_IMAGE_CROPPER_ID: Int = R.id.view_image_cropper
            fun with(activity: Activity): Helper {
                return Helper(activity)
            }
        }

        init {
            mImageCropper = ImageCropper(activity)
            mImageCropper.id = VIEW_IMAGE_CROPPER_ID
            activityDecorView = activity.window.decorView as ViewGroup
        }
    }

    companion object {
        @Throws(Exception::class)
        private fun obtainBmpSource(@NonNull context: Context, @NonNull uri: Uri, requiredWidth: Int, requiredHeight: Int): Bitmap? {
            val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, "r") ?: return null
            val fileDescriptor = parcelFileDescriptor.fileDescriptor
            val options = BitmapFactory.Options()
            options.inJustDecodeBounds = true
            BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options)
            //        options.inSampleSize = calculateInSampleSize(options, Candy.INSTANCE.getScreenWidth(), Candy.INSTANCE.getScreenWidth());
            options.inJustDecodeBounds = false
            var decodeSampledBitmap: Bitmap? = null
            var success = false
            while (!success) {
                try {
                    decodeSampledBitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options)
                    success = true
                } catch (error: OutOfMemoryError) {
                    error.printStackTrace()
                    // Log.e("ImageCropper", "decodeSampledBitmap: BitmapFactory.decodeFileDescriptor: ", error);
                    options.inSampleSize++
                }
            }
            close(parcelFileDescriptor)
            val exifOrientation = getExifOrientation(context, uri)
            val exifDegrees = exifToDegrees(exifOrientation)
            val exifTranslation = exifToTranslation(exifOrientation)
            val matrix = Matrix()
            if (exifDegrees != 0) {
                matrix.preRotate(exifDegrees.toFloat())
            }
            if (exifTranslation != 1) {
                matrix.postScale(exifTranslation.toFloat(), 1f)
            }
            if (!matrix.isIdentity) {
                decodeSampledBitmap = transformBitmap(decodeSampledBitmap, matrix)
            }
            return decodeSampledBitmap
        }

        private fun transformBitmap(@NonNull bitmap: Bitmap?, @NonNull transformMatrix: Matrix): Bitmap? {
            var bitmap = bitmap ?: return null
            try {
                val converted = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, transformMatrix, true)
                if (bitmap != converted) {
                    bitmap.recycle()
                    bitmap = converted
                }
            } catch (error: OutOfMemoryError) {
                error.printStackTrace()
                // Log.e("ImageCropper", "transformBitmap: ", error);
            }
            return bitmap
        }

        private fun calculateInSampleSize(@NonNull options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
            // Raw height and width of image
            val height = options.outHeight
            val width = options.outWidth
            var inSampleSize = 1
            if (height > reqHeight || width > reqWidth) {
                // Calculate the largest inSampleSize value that is a power of 2 and keeps both
                // height and width lower or equal to the requested height and width.
                while (height / inSampleSize > reqHeight || width / inSampleSize > reqWidth) {
                    inSampleSize *= 2
                }
            }
            return inSampleSize
        }

        private fun getExifOrientation(@NonNull context: Context, @NonNull imageUri: Uri): Int {
            var orientation = ExifInterface.ORIENTATION_UNDEFINED
            try {
                val stream = context.contentResolver.openInputStream(imageUri) ?: return orientation
                orientation = ImageHeaderParser(stream).orientation
                close(stream)
            } catch (e: Throwable) {
                e.printStackTrace()
                // Log.e("ImageCropper", "getExifOrientation: " + imageUri.toString(), e);
            }
            return orientation
        }

        private fun exifToDegrees(exifOrientation: Int): Int {
            return when (exifOrientation) {
                ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSPOSE -> 90
                ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_VERTICAL -> 180
                ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSVERSE -> 270
                else -> 0
            }
        }

        private fun exifToTranslation(exifOrientation: Int): Int {
            return when (exifOrientation) {
                ExifInterface.ORIENTATION_FLIP_HORIZONTAL, ExifInterface.ORIENTATION_FLIP_VERTICAL, ExifInterface.ORIENTATION_TRANSPOSE, ExifInterface.ORIENTATION_TRANSVERSE -> -1
                else -> 1
            }
        }

        private fun close(@Nullable c: Closeable?) {
            if (c != null) { // java.lang.IncompatibleClassChangeError: interface not implemented
                try {
                    c.close()
                } catch (e: Throwable) {
                    // silence
                }
            }
        }
    }

    init {
        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
        mGestureDetector = GestureDetector(context, this)
        LayoutInflater.from(context).inflate(R.layout.layout_image_cropper, this)
        findViewById<View>(R.id.viewBack).also { iBack = it }.setOnClickListener(this)
        findViewById<View>(R.id.viewSubmit).also { iSubmit = it }.setOnClickListener(this)
        tTitle = findViewById(R.id.viewTitle)
        iSource = findViewById(R.id.viewSource)
        vOverlay = findViewById(R.id.viewOverlay)
        setBackgroundColor(Color.BLACK)
        visibility = View.INVISIBLE
    }
}
